# -*- coding: utf-8 -*-
# 微信群文字导出机器人（定时版）

from wxauto import WeChat
import pyautogui
import win32gui, win32con
import time, os
from datetime import datetime, timedelta
import re
from enum import Enum
import random
from wxname2studentid import Wxname2StudentID

# === 请在这里设置你的群名称 ===
# GROUP_NAME = {"英语群"}   # 例：微信群名称
# GROUP_NAME = {"数学群"}   # 例：微信群名称
GROUP_NAME = {"语文群", "英语群", "数学群"}   # 例：微信群名称
SAVE_DIR = ".\\WeChatTextExport"  # 导出目录，可自行修改
SCROLL_PIXEL = 5000 # 向上翻页行数
SCROLL_INTERVAL = 0.5 # 等待秒数
SCROLL_TIMES = 50 # 翻次数
MAPPING_EXCEL_NAME = "mapping.xlsx"

# 创建目标数字集合
def create_number_set():
    s1 = set(range(29101, 29119))   # 29101–29118
    s2 = set(range(29151, 29183))   # 29151–29182
    return s1.union(s2)

# 检查字符串中包含哪些数字，如果找到则从集合删除
def remove_found_numbers(student_id, num_set):
    num_set.discard(student_id)    # 从集合中删除找到的数字
    return num_set

# 从一个字符串中提取出5位数学号和剩下的打卡内容字符串
# 提取字符串中所有数字串（用 re.findall）。
# 在这些数字中找出哪些属于合法学号集合 nums。
# 如果找到多个合法学号，就取最靠前的一个。
# 根据该学号在原字符串中的位置，提取前后内容，组合为 check_in。
def get_student_id_from_string(input_string):
    nums = create_number_set()

    # 找出字符串中的所有数字
    all_numbers = re.findall(r"\d+", input_string)
    
    # 筛选出在 nums 集合中的学号
    valid_ids = [num for num in all_numbers if int(num) in nums]

    # 如果没有合法学号，返回0和空字符串
    if not valid_ids:
        return "", ""

    # 如果有多个合法学号，取第一个在原字符串中出现的位置最靠前的
    first_valid_id = min(valid_ids, key=lambda x: input_string.find(x))

    # 找到学号在原字符串中的位置
    idx = input_string.find(first_valid_id)

    # 提取学号前后的文本
    before = input_string[:idx].strip()
    after = input_string[idx + len(first_valid_id):].strip()

    # 打卡内容为除去学号的剩余部分
    check_in = (before + " " + after).strip()

    return first_valid_id, check_in

# def get_student_id_from_string(input_string):
#    nums = create_number_set()
#
#    # 允许字符串前面有空格或制表符，学号仍需在开头（去掉前导空格后）
#    match = re.match(r"^\s*(\d+)[\s,;]*([\w\W]*)", input_string)
#        
#    if match and int(match.group(1)) in nums:
#        student_id = match.group(1).strip()  # 学号
#        check_in = match.group(2).strip()    # 打卡内容
#    else:
#        return 0, ""  # 如果无法提取学号或内容
#    return student_id, check_in

# 创建保存目录
if not os.path.exists(SAVE_DIR):
    os.makedirs(SAVE_DIR)

def is_target_date(date_string, target_date):
    # 1. 处理带 AM/PM 格式的时间（例如 "03:30 PM"）
    try:
        if "AM" in date_string or "PM" in date_string:
            date_format = "%I:%M %p"  # 12小时制时间（包含 AM/PM）
            input_datetime = datetime.strptime(date_string, date_format)
            input_datetime = input_datetime.replace(year=datetime.today().year, month=datetime.today().month, day=datetime.today().day)
            return (input_datetime.date() - target_date.date()).days
    except ValueError:
        pass  # 如果格式不匹配，就跳过这个异常，继续处理其他格式

    # 2. 处理带毫秒的时间（例如 "2025-10-13 15:30:00.123"）
    try:
        if "." in date_string:
            date_format = "%Y-%m-%d %H:%M:%S.%f"  # 带毫秒的时间格式
            input_datetime = datetime.strptime(date_string, date_format)
            return (input_datetime.date() - target_date.date()).days
    except ValueError:
        pass

    # 3. 处理完整日期时间（例如 "2025-10-13 15:30:00"）
    try:
        if " " in date_string:
            date_format = "%Y-%m-%d %H:%M:%S"  # 完整的日期时间格式
            input_datetime = datetime.strptime(date_string, date_format)
            return (input_datetime.date() - target_date.date()).days
    except ValueError:
        pass

    # 4. 处理没有日期的时间字符串（例如 "15:30"），默认是今天
    if ":" in date_string and len(date_string) <= 5:  # "HH:MM" 格式
        input_datetime = datetime.strptime(datetime.today().strftime("%Y-%m-%d") + " " + date_string, "%Y-%m-%d %H:%M")
        return (input_datetime.date() - target_date.date()).days

    # 5. 处理只包含日期的字符串（例如 "2025-10-13"）
    try:
        date_format = "%Y-%m-%d"
        input_datetime = datetime.strptime(date_string, date_format)
        return (input_datetime.date() - target_date.date()).days
    except ValueError:
        pass

    return -1  # 如果所有格式都不匹配，返回 False

# 遍历微信窗口
def enum_windows(hwnd, results):
    window_text = win32gui.GetWindowText(hwnd)
    if window_text == "微信":  # 只筛选微信窗口
        # 获取窗口的层级，0 表示底层，较大值表示上层
        layer = win32gui.GetWindowLong(hwnd, win32con.GWL_STYLE)
        results.append((hwnd, window_text, layer))

# 找到微信主窗口
def get_wechat_main_window():
    results = []
    win32gui.EnumWindows(enum_windows, results)
    
    # 排序，选择层级较高的窗口（假设主窗口在最上层）
    if results:
        # 按层级降序排列，获取层级最高的窗口
        sorted_results = sorted(results, key=lambda x: x[2], reverse=True)
        main_window = sorted_results[0][0]  # 获取层级最高的窗口句柄
        return main_window
    
    raise Exception("未找到微信主窗口")
    
# 激活微信主窗口到前台
def bring_wechat_to_front(hwnd):
    win32gui.ShowWindow(hwnd, 5)
    win32gui.SetForegroundWindow(hwnd)

# 判断是否为纯文本消息
def is_text_message(msg):
    return msg.type == "text" or msg.type == "quote"

# 判断是否为图片消息
def is_pic_message(msg):
    return msg.type == "image"

# 判断是否为时间消息
def is_time_message(msg):
    return msg.message_type_name == "TimeMessage"

# 判断是否为文件消息
def is_file_message(msg):
    return msg.type == "file"

# 判断是否为笔记消息
def is_memo_message(msg):
    return msg.type == "other" and "笔记" in msg.content

# 发送结果到微信群中
def send_string_message_to_group(wx, message):
    if wx:
        # 发送消息到该群
        wx.SendMsg(message)
        print(f"Message sent: {message}")
    else:
        print(f"Message sent failed.")

# 把文本文件的内容发送到微信群中
def send_file_message_to_group(wx, file_path):
    if wx:
        # 读取文本文件的内容
        try:
            with open(file_path, "r", encoding="utf-8-sig") as file:
                file_content = file.read()  # 读取整个文件的内容
                # 将文件内容发送到群里
                wx.SendMsg(file_content)
                print(f"Message sent {file_content[:50]}...")  # 打印前50个字符
        except Exception as e:
            print(f"Error reading file: {e}")
    else:
        print(f"Message sent failed.")

# 状态机
# 英语群打卡时需要用到状态机，数学群和语文群不需要
class State(Enum):
    Start = 0 # 开始状态
    ImageProcceeded = 1 # 处理了图片，等待备注文本
    TextProceeded = 2 # 处理了文本，等待图片或文件
    FileProceeded = 3 # 处理了文件，等待备注文本
    Success = 4 # 打卡成功，即既有图片又有备注文本，或者既有文件又有备注
    Error = -1 # 出错状态

class StateMachine:
    def __init__(self, name: str, state: State = State.Start):
        self.name = name
        self.state = state
        self.successed = False #这个变量用于判断是不是打卡同学不写备注只发图片
    
    # 状态机里的状态根据input相应转换
    def transition(self, input: str):
        if self.state == State.Start:
            if input == "image":
                self.state = State.ImageProcceeded
            elif input == "text":
                self.state = State.TextProceeded
            elif input == "file":
                self.state = State.FileProceeded
            else:
                self.state = State.Start
        elif self.state == State.ImageProcceeded:
            if input == "image":
                self.state = State.ImageProcceeded
            elif input == "file":
                self.state = State.FileProceeded
            elif input == "text":
                self.state = State.Success
                self.successed = True #只要打卡成功过一次，就认为曾经成功过
            else:
                self.state = State.Error
        elif self.state == State.TextProceeded:
            if input == "image":
                self.state = State.Success
                self.successed = True #只要打卡成功过一次，就认为曾经成功过
            elif input == "file":
                self.state = State.Success
                self.successed = True #只要打卡成功过一次，就认为曾经成功过
            elif input == "text":
                self.state = State.TextProceeded
            else:
                self.state = State.Error
        elif self.state == State.FileProceeded:
            if input == "image":
                self.state = State.ImageProcceeded
            elif input == "file":
                self.state = State.FileProceeded
            elif input == "text":
                self.state = State.Success
                self.successed = True #只要打卡成功过一次，就认为曾经成功过
            else:
                self.state = State.Error
        else:
            self.state = State.Error
    
    # 判断打卡是否成功
    def is_succeeded(self):
        return self.state == State.Success
    
    # 判断打卡是否基本成功，这种情况是给只提供图片没有提供任何备注文本的家长
    def is_almost_succeeded(self):
        return self.state == State.ImageProcceeded and self.successed == False

    # 重新开始状态机
    def reset(self):
        self.state = State.Start

class Group:
    def __init__(self, name: str, msgs: list = None, target_date: datetime = None):
        self.name = name
        if msgs is None:
            msgs = []
        if target_date is None:
            target_date = datetime.today()
        self.msgs = msgs
        self.target_date = target_date

    def save_text_csv(self, text_msgs):
        pass
    def classify_check_in_from_string(self, input_string):
        pass

    # 从微信消息中提取有用的消息记录下来
    def export_group(self, mapping):
        # 因为有些家长不会把群昵称改成含有5位数学号，所以仅仅通过他们发送的备注信息，来判断他们可能是哪位学生的家长
        # 先过一遍，找到那些群昵称里没有学号的家长们，先建立一个映射
        for m in self.msgs:
            if (is_text_message(m) and (not m.attr == "self") and (not m.content.startswith('@'))) or \
            ((is_pic_message(m) or is_file_message(m) or is_memo_message(m)) and not m.attr == "self"):
                student_id1, _ = get_student_id_from_string(m.sender_remark)
                student_id2, _ = get_student_id_from_string(m.content)
                # 看谁在群里说话，如果说话的人有含学号的群昵称，则refresh mapping
                if student_id1:
                    id = mapping.get(m.sender)
                    if not id:
                        mapping[m.sender] = student_id1
                    elif int(id) != int(student_id1):
                        # 下面这种情况是异常情况，一个家长的孩子换学号了？
                        print(f"学生{id}和学生{student_id1}的家长微信竟然同名{m.sender}，请确认！")
                        # 不更新mapping
                        raise Exception(f"学生{id}和学生{student_id1}的家长微信竟然同名{m.sender}")
                else:
                    if student_id2:
                        id = mapping.get(m.sender)
                        if not id:
                            mapping[m.sender] = student_id2
                        elif int(id) != int(student_id2):
                            # 下面这种情况是异常情况，只能说这次通话该家长可能是笔误了
                            print(f"{m.sender}有可能笔误，{id}可能在帮{student_id2}打卡。请确认！")
                            # 不更新mapping

        # 真正运行提取有用的微信记录给后面的打卡程序判断处理
        text_msgs = []
        istarget_date = 0
        # 检查图片发送者昵称以及文本消息
        for m in self.msgs:
            if is_time_message(m):
                istarget_date = is_target_date(m.time, self.target_date)
            if istarget_date < 0:
                if text_msgs:
                    text_msgs = []
                continue
            elif istarget_date > 0:
                break
            # 记录规则：
            # 如果发送的是一张图片，
            #    如果是自己发的，打卡不成功
            #    否则，如果是某人发的，则记录下来该发送者的昵称
            #        如果该发送者昵称不含有效学号，打卡不成功
            #        否则，即该发送者昵称含有有效学号，打卡成功
            # 否则，如果发送的是一句文本对话，
            #    如果是自己发的，打卡不成功
            #    否则，如果是某人发的，但是发送内容里含有@，表示的是@别人，打卡不成功
            #    否则，如果发送者昵称里没有含有有效学号，且发送内容里也不含有有效学号，则打卡不成功
            #    否则，如果发送者昵称里没有含有有效学号，但发送内容里含有有效学号，则打卡成功
            #    否则，如果发送者昵称里含有有效学号，但发送内容里没有有效学号，或者发送内容里含有有效学号但不一致，则打卡不成功
            #    否则打卡成功
            # 否则，如果发送的是其它内容微信，则忽略，打卡不成功

            # 如果是一个文本对话，就记录下来
            if is_text_message(m):
                # 不能是程序自己发的话，且也不能是@开始@别人的话
                if not m.attr == "self" and not m.content.startswith('@'):
                    # 比较一下某个群昵称的话里的学号，是不是与群昵称里的学号一致，不允许帮打止的情况发生
                    # 如果某人群昵称没有学号，只有讲的话里有学号信息，对话就加进来
                    # 或如果某人群昵称有学号，只有讲的话里有学号信息且等于昵称里学号信息，对话才加进来
                    student_id1, _ = get_student_id_from_string(m.sender_remark)
                    if not student_id1:
                        student_id1 = mapping.get(m.sender, "")

                    student_id2, _ = get_student_id_from_string(m.content)
                    if not student_id1:
                        if student_id2:
                            text_msgs.append(m.content)
                    else:
                        if student_id2 and int(student_id1) == int(student_id2):
                            text_msgs.append(m.content)
            # 如果是一张图片或文件，也记录下来
            elif is_pic_message(m) or is_file_message(m) or is_memo_message(m):
                if not m.attr == "self":
                    # 如果sender_remark里不含有学号信息，则用前面建立的映射表替代群昵称，否则就用群昵称
                    student_id1, _ = get_student_id_from_string(m.sender_remark)
                    if student_id1:
                        text_msgs.append(student_id1 + m.content)
                    else:
                        student_id1 = mapping.get(m.sender)
                        if student_id1:
                            text_msgs.append(student_id1 + m.content)
                        else:
                            text_msgs.append(m.sender_remark + m.content)

        return text_msgs 

class EnglishGroup(Group):
    def __init__(self, name="英语群", msgs: list = None, target_date: datetime = None):
        super().__init__(name, msgs, target_date)

    def update_student_records(self, student_records, student_id, word_memorization, text_recitation, grammar_study):
        # 更新为文字打卡结果（取最大）
        student_records[student_id][0] = max(student_records[student_id][0], word_memorization)
        student_records[student_id][1] = max(student_records[student_id][1], text_recitation)
        student_records[student_id][2] = max(student_records[student_id][2], grammar_study)

    # 保存纯文字消息到当天csv文件
    def save_text_csv(self, text_msgs):
        nums = create_number_set()
        nums_length = len(nums)
        target_date = self.target_date.strftime("%Y-%m-%d")
        filename_txt = os.path.join(SAVE_DIR, f"{self.name}_{target_date}.txt")
        filename_csv = os.path.join(SAVE_DIR, f"{self.name}_{target_date}.csv")

        # 打卡状态机集合
        sm = {}
        # 存储学号和对应的打卡记录
        student_records = {}
        shadow_records = {} # 有些人会先打备注再发图片，先用一个影子暂存一下，直到状态机打卡成功了，再写到student_records中去

        for num in nums:
            student_records[num] = [0, 0, 0, 0]  # 初始化三类的计数为 [0, 0, 0, 0]
            shadow_records[num] = [0, 0, 0]
            sm[num] = StateMachine(f"{num}的英语打卡状态机")
        
        with open(filename_txt, "a", encoding="utf-8") as f1, open(filename_csv, "w", encoding="utf-8-sig") as f2:
            for text_msg in text_msgs:
                f1.write(text_msg)
                f1.write("\n")
                student_id, csv_row = self.classify_check_in_from_string(text_msg)
                
                # 只处理有效的记录
                if student_id:
                    word_memorization, text_recitation, grammar_study, media = map(int, csv_row.split(",")[1:])

                    if media == 1: # 图片
                        sm[student_id].transition("image")
                    elif media == 2: # 文件
                        sm[student_id].transition("file")
                    else:
                        if word_memorization == 1 or text_recitation == 1 or grammar_study == 1:
                            sm[student_id].transition("text")
                            # 暂存
                            shadow_records[student_id][0] = word_memorization
                            shadow_records[student_id][1] = text_recitation
                            shadow_records[student_id][2] = grammar_study
                    
                    if not sm[student_id].is_succeeded():
                        continue

                    # 处理打卡
                    word_memorization = shadow_records[student_id][0]
                    text_recitation = shadow_records[student_id][1]
                    grammar_study = shadow_records[student_id][2]
                    # 把暂存的写到真正的student_records中去，commit
                    self.update_student_records(student_records, student_id, word_memorization, text_recitation, grammar_study)
                    nums = remove_found_numbers(student_id, nums)
                    sm[student_id].reset()

            # 最后在剩下的再检查一下，还有哪个学生是只用图片打卡的
            oldnums = nums.copy()
            for num in nums:
                if sm[num].is_almost_succeeded():
                    # 如果是图片判断的打卡逻辑，随机取个1，其它两个则为0的分类
                    values = [0, 0, 0]
                    values[random.randint(0, 2)] = 1
                    self.update_student_records(student_records, num, values[0], values[1], values[2])
                    oldnums = remove_found_numbers(num, oldnums)
            nums = oldnums
      
            # 排序学号
            sorted_student_ids = sorted(student_records.keys(), key=int)
            
            f2.write("学号,当天单词词组默写次数,当天课文背诵次数,当天语法知识点学习次数\n")
            # 输出最终结果到CSV文件
            for student_id in sorted_student_ids:
                row = f"{student_id},{student_records[student_id][0]},{student_records[student_id][1]},{student_records[student_id][2]}"
                f2.write(row)
                f2.write("\n")
            
        # 未打卡的学生
        write_nums = nums_length - len(nums)
        left_nums = len(nums)
        
        message = f"✅ 共有 {write_nums} 位同学打卡"
        message = message + "\n" + f"⚠️ 还有 {left_nums} 位同学，即 {sorted(nums)} 可能没打卡"
        print(message)
        return message, filename_csv


    # 从英语群字符串中提取学号和打卡内容
    def classify_check_in_from_string(self, input_string):
        student_id, check_in = get_student_id_from_string(input_string)
        if not student_id:
            return 0, check_in
        
        # 初始化三个类别的计数为0
        word_memorization = 0
        text_recitation = 0
        grammar_study = 0
        media = 0 # text
        if "[图片]" in check_in:
            media = 1 # image
        elif "[文件]" in check_in or "[笔记]" in check_in: # 增加了【笔记】
            media = 2 # file

        # 归类优先级：1. 课文背诵类，2. 语法知识点学习类，3. 单词词组默写类
        # 课文背诵类
        if "课文" in check_in or "背诵" in check_in or "背默" in check_in:
            text_recitation = 1
        # 语法知识点学习类
        elif any(keyword in check_in for keyword in ["语法", "笔记", "作业", "语言点", "拓展", "卷子", "阅读", "完形", "完形填空", "选词填空", "知识点", "题目"]):
            grammar_study = 1
        # 单词词组默写类
        elif any(keyword in check_in for keyword in ["单词", "词组", "词汇", "词语", "默写", "听写", "动词变形", "动词不规则", "重默", "翻译"]):
            word_memorization = 1
        # 默认归类为单词词组默写类（如果没有匹配）
        else:
            word_memorization = 1

        # 返回学号和四元组字符串（含图片判断位）
        return int(student_id), f"{student_id}, {word_memorization}, {text_recitation}, {grammar_study}, {media}"

"""     def export_group(self):
        text_msgs = []
        istarget_date = 0

        # 只保留文字消息
        for m in self.msgs:
            if is_time_message(m):
                istarget_date = is_target_date(m.time, self.target_date)
            if istarget_date < 0:
                if text_msgs:
                    text_msgs = []
                continue
            elif istarget_date > 0:
                break

            # 记录规则：
            # 如果发送的是一张图片，
            #    如果是自己发的，打卡不成功
            #    否则，如果是某人发的，则记录下来该发送者的昵称
            #        如果该发送者昵称不含有效学号，打卡不成功
            #        否则，即该发送者昵称含有有效学号，打卡成功
            # 否则，如果发送的是一句文本对话，
            #    如果是自己发的，打卡不成功
            #    否则，如果是某人发的，但是发送内容里含有@，表示的是@别人，打卡不成功
            #    否则，如果发送者昵称里没有含有有效学号，且发送内容里也不含有有效学号，则打卡不成功
            #    否则，如果发送者昵称里没有含有有效学号，但发送内容里含有有效学号，则打卡成功
            #    否则，如果发送者昵称里含有有效学号，但发送内容里没有有效学号，或者发送内容里含有有效学号但不一致，则打卡不成功
            #    否则打卡成功
            # 否则，如果发送的是其它内容微信，则忽略，打卡不成功

            # 如果是一个文本对话，就记录下来
            if is_text_message(m):
                # 不能是程序自己发的话，且也不能是@开始@别人的话
                if not m.attr == "self" and not m.content.startswith('@'):
                    # 比较一下某个群昵称的话里的学号，是不是与群昵称里的学号一致，不允许帮打止的情况发生
                    # 如果某人群昵称没有学号，只有讲的话里有学号信息，对话就加进来
                    # 或如果某人群昵称有学号，只有讲的话里有学号信息且等于昵称里学号信息，对话才加进来
                    student_id1, _ = get_student_id_from_string(m.sender_remark)
                    student_id2, _ = get_student_id_from_string(m.content)
                    if (int(student_id1) == 0 and int(student_id2) > 0) or (int(student_id1) > 0 and int(student_id1) == int(student_id2)):
                        text_msgs.append(m.content)
            # 如果是一张图片，也记录下来
            elif is_pic_message(m):
                if not m.attr == "self":
                    text_msgs.append(m.sender_remark)

        return text_msgs 
 """
class MathsGroup(Group):
    def __init__(self, name="数学群", msgs: list = None, target_date: datetime = None):
        super().__init__(name, msgs, target_date)

    # 保存消息到当天csv文件
    def save_text_csv(self, text_msgs):
        nums = create_number_set()
        nums_length = len(nums)
        target_date = self.target_date.strftime("%Y-%m-%d")
        filename_txt = os.path.join(SAVE_DIR, f"{self.name}_{target_date}.txt")
        filename_csv = os.path.join(SAVE_DIR, f"{self.name}_{target_date}.csv")
        
        # 存储学号和对应的打卡记录，反正就是“打卡”及“没打卡”两种结果，无需暂存
        student_records = {}
        for num in nums:
            student_records[num] = [0]  # 初始化计算为[0]
        
        with open(filename_txt, "a", encoding="utf-8") as f1, open(filename_csv, "w", encoding="utf-8-sig") as f2:
            for text_msg in text_msgs:
                f1.write(text_msg)
                f1.write("\n")
                student_id, csv_row = self.classify_check_in_from_string(text_msg)
                
                # 只处理有效的记录
                if student_id:          
                    # 更新学号的打卡记录
                    check_in = int(csv_row.split(",")[1])
                    # 如果只有文字备注，没有发现该学生有图片，则打卡不成功
                    if check_in == 1:
                        student_records[student_id][0] = max(student_records[student_id][0], check_in)
                        nums = remove_found_numbers(student_id, nums)
            
            # 排序学号
            sorted_student_ids = sorted(student_records.keys(), key=int)
            
            f2.write(f"学号,当天{self.name[0:2]}打卡\n")
            # 输出最终结果到CSV文件
            for student_id in sorted_student_ids:
                row = f"{student_id},{student_records[student_id][0]}"
                f2.write(row)
                f2.write("\n")
            
        # 未打卡的学生
        write_nums = nums_length - len(nums)
        left_nums = len(nums)
        
        message = f"✅ 共有 {write_nums} 位同学打卡"
        message = message + "\n" + f"⚠️ 还有 {left_nums} 位同学，即 {sorted(nums)} 可能没打卡"
        print(message)
        return message, filename_csv


    # 从数学群字符串中提取学号和打卡内容
    def classify_check_in_from_string(self, input_string):
        student_id, check_in = get_student_id_from_string(input_string)
        if not student_id:
            return 0, check_in
        
        # 如果只有文字备注，没有发现该学生有图片，则打卡不成功
        ret = 0
        if "[图片]" in check_in or "[笔记]" in check_in: # 增加了【笔记】，但绝不能是文件，否则那些分享上课笔记的人将会被误判
            ret = 1

        return int(student_id), f"{student_id}, {ret}"

class GroupFactory:
    def create_group(self, groupname: str, msgs: list, target_date: datetime) -> Group:
        if groupname == "英语群":
            return EnglishGroup(msgs=msgs, target_date=target_date)
        elif groupname == "数学群":
            return MathsGroup(msgs=msgs, target_date=target_date)
        elif groupname == "语文群":
            return MathsGroup(name=groupname, msgs=msgs, target_date=target_date)
        elif groupname == "测试群":
            return MathsGroup(name=groupname, msgs=msgs, target_date=target_date)
        else:
            raise ValueError("未知的微信群")

def main():
    # 用户输入完整日期
    yesterday_date = datetime.today() - timedelta(days=1)
    date_str = input(f"请输入要生成的日期（格式 yyyy-mm-dd 或 yyyy/mm/dd，例如 {yesterday_date.strftime("%Y-%m-%d")}）：").strip()

    # 兼容不同日期格式
    date_formats = ["%Y-%m-%d", "%Y/%m/%d", "%Y%m%d"]
    target_date = None
    for fmt in date_formats:
        try:
            target_date = datetime.strptime(date_str, fmt)
            break
        except ValueError:
            continue

    if target_date is None:
        print("❌ 无法识别输入的日期格式，请输入类似 2025-10-27 或 20251027，那么就导入今天的。")
        # target_date = datetime.today()
        target_date = yesterday_date
  
    # 开始导出
    print(f"🚀 启动微信群文字导出：{GROUP_NAME}")
    wx = WeChat()

    wx2id = Wxname2StudentID(os.path.join(SAVE_DIR, MAPPING_EXCEL_NAME))
    mapping = wx2id.load_mapping()

    factory = GroupFactory()
    for groupname in GROUP_NAME:
        resp = input(f"要处理{groupname}吗？(y/n，默认 y)：").strip().lower()
        if resp == "":
            resp = "y"   # 用户直接回车时，默认设为 y
        if resp != "y":
            continue

        wx.ChatWith(groupname)  # 打开指定群
        print(f"🟢 已打开群：{groupname}，开始抓取文字消息...")

        hwnd = get_wechat_main_window()
        bring_wechat_to_front(hwnd)

        for _ in range(SCROLL_TIMES):
            pyautogui.scroll(SCROLL_PIXEL)
            time.sleep(SCROLL_INTERVAL)

        msgs = wx.GetAllMessage() or []
    
        group = factory.create_group(groupname, msgs, target_date)
        text_msgs = group.export_group(mapping)

        if text_msgs:
            summary, file_path = group.save_text_csv(text_msgs)
            # 用户输入是否输出到微信里
            resp = input(f"⚠️ 要输出结果到微信群里吗 (y/n)：").strip().lower()
            if resp == "y":
                summary = f"【系统消息】 {target_date.strftime("%Y-%m-%d")} 统计结果如下：" + "\n" + summary
                send_string_message_to_group(wx, summary)
                # 不再发送文件到群中，只发送统计结果
                # send_file_message_to_group(wx, file_path)
            print(f"{groupname}导出完毕！")
        else:
            print(f"⚠️ 未检测到新的文字消息，请确认微信群{groupname}已打开。")

    wx2id.save_mapping(mapping)

if __name__ == "__main__":
    main()
